package cn.initcap.algorithm.leetcode;

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;

/**
 * 【指数平滑法】St=aYt-1+(1-a)St-1
 * 指数平滑法实际上是一种特殊的加权移动平均法。其特点是: 第一，指数平滑法进一步加强了观察期近期观察值对预测值的作用，对不同时间的
 * 观察值所赋予的权数不等，从而加大了近期观察值的权数，使预测值能够迅速反映市场实际的变化。权数之间按等比级数减少，此级数之首项为
 * 平滑常数a,公比为(1- a)。第二，指数平滑法对于观察值所赋予的权数有伸缩性，可以取不同的a 值以改变权数的变化速率。如a取小值，则
 * 权数变化较迅速，观察值的新近变化趋势较能迅速反映于指数移动平均值中。因此，运用指数平滑法，可以选择不同的a 值来调节时间序列观察
 * 值的均匀程度(即趋势变化的平稳程度)
 *
 * @author initcap
 * @date Created in 1/17/19 10:37 PM.
 */
public class ExponentialSmoothing {

    public static void main(String[] args) {
        List<Double> values = new ArrayList<>(10);
        values.add(11D);
        values.add(9D);
        values.add(13D);
        values.add(2D);
        values.add(6D);
        for (int i = 0; i < 5; i++) {
            System.out.println(exponentialSmoothing2(values, 0.5, i + 1));
        }
    }

    /**
     * 一次指数平滑
     *
     * @param values 基础集合
     * @param a      [0,1]平滑常数
     * @return
     */
    private static List<Double> exponentialSmoothing(List<Double> values, double a) {
        List<Double> smoothingValues = new ArrayList<>();
        double last = 0;
        for (int i = 0; i < values.size(); i++) {
            double v = values.get(i);
            if (i == 0) {
                last = v;
            }
            last = a * v + (1 - a) * last;
            smoothingValues.add(last);
        }
        return smoothingValues;
    }

    /**
     * 二次指数平滑
     *
     * @param values 基础集合
     * @param a      [0,1]平滑常数
     * @param t      未来第几期
     * @return
     */
    private static Double exponentialSmoothing2(List<Double> values, double a, int t) {
        List<Double> smoothingValues1 = exponentialSmoothing(values, a);
        List<Double> smoothingValues2 = exponentialSmoothing(smoothingValues1, a);
        Double lastIndex = smoothingValues1.get(smoothingValues1.size() - 1);
        Double lastSecIndex = smoothingValues2.get(smoothingValues2.size() - 1);
        double at = 2 * lastIndex - lastSecIndex;
        double bt = (a / (1 - a)) * (lastIndex - lastSecIndex);
        double result = at + bt * t;

        DecimalFormat df = new DecimalFormat("#0.00");
        result = Double.parseDouble(df.format(result));
        return result;
    }

    /**
     *  * 二次指数平滑法求预测值
     *  * @param values 基础数据集合
     *  * @param t 未来第几期
     *  * @param a 平滑系数
     *  * @return 预测值
     *  
     */
    private static Double getExpect(List<Double> values, Double a, int t) {
        if (a <= 0 || a >= 1) {
            return null;
        }
        Double lastIndex = values.get(0);
        Double lastSecIndex = values.get(0);
        for (Double v : values) {
            lastIndex = a * v + (1 - a) * lastIndex;
            lastSecIndex = a * lastIndex + (1 - a) * lastSecIndex;
        }
        Double at = 2 * lastIndex - lastSecIndex;
        Double bt = (a / (1 - a)) * (lastIndex - lastSecIndex);
        Double result = at + bt * t;
        DecimalFormat df = new DecimalFormat("#0.00");
        result = Double.parseDouble(df.format(result));
        return result;
    }

}
